﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
using System.Timers;

namespace CodeIn.Modules.Graph.Modules.Ui.Flow.Engine {
	public class FlowEngine {
		public int Fps { get; private set; }
		public double Damping { get; set; }
		public double Repulsion { get; set; }
		public double Attraction { get; set; }

		private Timer timer;
		private long oldTicks;
		private long tempTicks;
		private int frameCount;
		private IList<Sprite> sprites;

		public event EventHandler Updated;

		public FlowEngine() {
			Damping = 0.85;
			Repulsion = 1000;
			Attraction = 0.03;

			sprites = new List<Sprite>();

			timer = new Timer(10);
			timer.Elapsed += (s, e) => {
				long currentTicks = DateTime.Now.Ticks;
				long ellapsedTicks = currentTicks - oldTicks;
				TimeSpan ellapsedTime = TimeSpan.FromTicks(ellapsedTicks);
				oldTicks = currentTicks;

				tempTicks += ellapsedTicks;
				frameCount += 1;

				if (TimeSpan.FromTicks(tempTicks).TotalSeconds > 1) {
					tempTicks = 0;
					Fps = frameCount;
					frameCount = 0;
				}

				Update(ellapsedTime);
				if (Updated != null)
					Updated(this, new EventArgs());
			};
		}

		public void Start() {
			timer.Start();
		}

		public void Stop() {
			timer.Stop();
		}

		public Sprite AddSprite(double radius) {
			Sprite sp = new Sprite();
			sp.Radius = radius;
			sprites.Add(sp);
			return sp;
		}

		private void Update(TimeSpan ellapsedTime) {
			for (int i=0; i<sprites.Count; i++) {
				Sprite sp = sprites[i];
				sp.A = new Vector(0, 0);

				if (sp.IsDragged)
					continue;

				for (int j = 0; j < sprites.Count; j++) {
					Sprite sp2 = sprites[j];

					if (sp2 != sp) {
						double direction = GetDirection(sp2.P, sp.P);
						double distance = GetDistance(sp2.P, sp.P);
						distance = distance < sp.Radius + sp2.Radius ? sp.Radius + sp2.Radius : distance;
						double force = Repulsion / (distance * distance);

						if (sp.Neighbors.Contains(sp2))
							force -= Attraction * (distance - sp.Radius - sp2.Radius);

						sp.A += force * new Vector(Math.Cos(direction), Math.Sin(direction));
					}
				}
			}

			for (int i = 0; i < sprites.Count; i++) {
				Sprite sp = sprites[i];
				sp.UpdatePosition(Damping);
			}
		}

		private double GetDirection(Point p1, Point p2) {
			return Math.Atan2(p2.Y - p1.Y, p2.X - p1.X);
		}

		private double GetDistance(Point p1, Point p2) {
			Vector d = p2 - p1;
			return d.Length;
		}
	}
}
